package my.servlet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.MethodParameterNamesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ConfigurationBuilder;

import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;

import annotation.Action;
import annotation.RequestMapper;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.LocalVariableAttribute;

/**
 * 类扫描工具
 * 
 * @author guojian
 *
 */
public class ClassScanUtil {

	private static final Log log = LogFactory.getLog(ClassScanUtil.class);

	public static final Map<String, RequestMapperBean> requestMapperMap = new HashMap<String, RequestMapperBean>();

	private String packageRex;

	private static final ClassPool pool = ClassPool.getDefault();

	static {
		pool.insertClassPath(new ClassClassPath(ClassScanUtil.class));
	}

	public void init() {
		log.debug("..初始化类扫描器...");
		Map<String, List<String>> packageClassMap = scanAllClassesByPackageNameRex(getPackageRex());
		scanAllRequestUri(packageClassMap);
		for (String requestUri : requestMapperMap.keySet()) {
			log.debug("requestMapperUri: " + requestUri + "-->" + requestMapperMap.get(requestUri).toString());
		}
	}

	/**
	 * 扫描所有注解中包含的请求路径
	 * 
	 * @param packageClassMap
	 */
	private void scanAllRequestUri(Map<String, List<String>> packageClassMap) {
		for (String packageName : packageClassMap.keySet()) {
			List<String> classNames = packageClassMap.get(packageName);
			try {
				for (String className : classNames) {
					String classRequestUri = "";
					Class<?> c = Class.forName(className);
					if (c.isAnnotationPresent(RequestMapper.class)) {
						RequestMapper reqUriAnnotation = c.getAnnotation(RequestMapper.class);
						classRequestUri = reqUriAnnotation.value().replaceAll("/", "");
					}
					Method[] methods = c.getDeclaredMethods();
					for (Method method : methods) {
						if (!method.isAnnotationPresent(RequestMapper.class)) {
							continue;
						}
						RequestMapper requestUri = method.getAnnotation(RequestMapper.class);
						RequestMapperBean requestMapper = new RequestMapperBean();
						requestMapper.setClassName(className);
						requestMapper.setMethodName(method.getName());
						Class<?>[] paramTypes = method.getParameterTypes();
						requestMapper.setParamTypes(paramTypes);
						String[] paramNames = new String[paramTypes.length];
						CtMethod cm = pool.getMethod(className, method.getName());
						int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
						LocalVariableAttribute attr = (LocalVariableAttribute) cm.getMethodInfo().getCodeAttribute().getAttribute(LocalVariableAttribute.tag);
						for (int i = 0; i < paramTypes.length; i++) {
							paramNames[i] = attr.variableName(i + pos);
						}
						requestMapper.setParamNames(paramNames);
						requestMapperMap.put("/" + classRequestUri + "/" + requestUri.value().replaceAll("/", ""), requestMapper);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 扫描指定包名的规则下的所有class,返回map集合{包名:class列表}
	 * 
	 * @param packageRex
	 */
	private Map<String, List<String>> scanAllClassesByPackageNameRex(String packageRex) {
		try {
			ClassPath classpath = ClassPath.from(Thread.currentThread().getContextClassLoader());
			ImmutableSet<ClassInfo> classes = classpath.getTopLevelClasses();
			Map<String, List<String>> packageClassesMap = new HashMap<String, List<String>>();
			for (ClassInfo classInfo : classes) {
				String packageName = classInfo.getPackageName();
				if (Pattern.matches(getPackageRex(), packageName) && isAction(classInfo)) {
					List<String> classesList = packageClassesMap.get(packageName);
					if (classesList == null) {
						classesList = new ArrayList<String>();
						packageClassesMap.put(packageName, classesList);
					}
					classesList.add(classInfo.getName());
				}
			}
			return packageClassesMap;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return Collections.emptyMap();
	}

	/**
	 * 判断是否是一个action
	 * 
	 * @param classInfo
	 * @return
	 */
	private boolean isAction(ClassInfo classInfo) {
		try {
			return classInfo.load().isAnnotationPresent(Action.class);
		} catch (Exception e) {
			return false;
		}
	}

	public String getPackageRex() {
		return packageRex;
	}

	public void setPackageRex(String packageRex) {
		this.packageRex = packageRex;
	}

	public static void main(String[] args) {
		Reflections reflections = new Reflections("cn.*", new MethodAnnotationsScanner(), new TypeAnnotationsScanner(), new SubTypesScanner(), new MethodParameterNamesScanner());
		
		Set<URL> urls = reflections.getConfiguration().getUrls();
		for (URL url : urls) {
			System.out.println("url: " + url);
		}
		Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Action.class);
		for (Class<?> action : classes) {
			RequestMapper request = action.getAnnotation(RequestMapper.class);
			System.out.println(action + "=RequestMapper==" + request.value());
		}

		Set<Method> methods = reflections.getMethodsAnnotatedWith(RequestMapper.class);
		for (Method method : methods) {
			System.out.println(method.getName() + "=methods==" + reflections.getMethodParamNames(method));
		}
	}
}
